import 'package:escooter_patcher_flutter/base/base.dart';
import 'package:escooter_patcher_flutter/ble/ble_plugin.dart';
import 'package:escooter_patcher_flutter/prompt.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:version/version.dart';

class OperationController extends BaseController {
  List<String> regions = [
    "German(20km/h)",
    "Europe(25km/h)",
    "WorldWide(30km/h)",
    "Unlimited Speed\uD83D\uDE80\uD83D\uDE80\uD83D\uDE80"
  ];

  var maxSpeeds = [2000, 2500, 3000, 4000];

  static List<int> getSnBytes = [0xaa, 0xaa, 0x11, 0x02, 0x02, 0x02, 0x13];
  static List<int> getModelBytes = [0xaa, 0xaa, 0x11, 0x02, 0x02, 0x01, 0x10];
  static List<int> getVersionBytes = [0xaa, 0xaa, 0x11, 0x02, 0x02, 0x06, 0x17];

  static List<int> getSafeSpeedLimitOffBytes = [
    0xaa,
    0xaa,
    0x16,
    0x04,
    0x02,
    0x32,
    0x56,
    0x00,
    0x74
  ];

  static List<int> getRegionBytes = [
    0xaa,
    0xaa,
    0x13,
    0x05,
    0x01,
    0x32,
    0x01,
    0x05,
    0x02,
    0x23
  ];

  static List<int> setDesignedMaxSpeed20 = [
    0xaa,
    0xaa,
    0x13,
    0x08,
    0x01,
    0x32,
    0x01,
    0x85,
    0x02,
    0x02,
    0xD0,
    0x07,
    0x7b
  ];
  static List<int> setDesignedMaxSpeed25 = [
    0xaa,
    0xaa,
    0x13,
    0x08,
    0x01,
    0x32,
    0x01,
    0x85,
    0x02,
    0x02,
    0xC4,
    0x09,
    0x61
  ];
  static List<int> setDesignedMaxSpeed30 = [
    0xaa,
    0xaa,
    0x13,
    0x08,
    0x01,
    0x32,
    0x01,
    0x85,
    0x02,
    0x02,
    0xB8,
    0x0b,
    0x1f
  ];
  static List<int> setDesignedMaxSpeed35 = [
    0xaa,
    0xaa,
    0x13,
    0x08,
    0x01,
    0x32,
    0x01,
    0x85,
    0x02,
    0x02,
    0x10,
    0x0e,
    0xb2
  ];
  static List<int> setDesignedMaxSpeed38 = [
    0xaa,
    0xaa,
    0x13,
    0x08,
    0x01,
    0x32,
    0x01,
    0x85,
    0x02,
    0x02,
    0xD8,
    0x0E,
    0x7a
  ];
  static List<int> setDesignedMaxSpeed40 = [
    0xaa,
    0xaa,
    0x13,
    0x08,
    0x01,
    0x32,
    0x01,
    0x85,
    0x02,
    0x02,
    0xA0,
    0x0F,
    0x03
  ];

  var currentModel = unknownModel.obs;
  var sn = "--".obs;
  var version = "--".obs;
  var currentRegion = "--".obs;

  bool donNotSupportChangeRegion = false;

  final BleManager _bleManager = BleManager();

  SharedPreferences? _prefs;

  void _compactRegionsInfo(VehicleModel model) {
    if (model == inmotionC1Pro || model == inmotionC1Pro2) {
      regions[2] = "WorldWide(35km/h)";
      maxSpeeds[2] = 3500;
    } else if (model == inmotionE1) {
      regions[2] = "WorldWide(38km/h)";
      maxSpeeds[2] = 3800;
    } else {
      regions[2] = "WorldWide(30km/h)";
      maxSpeeds[2] = 3000;
    }
  }

  @override
  void init() async {
    _bleManager.onNotifiedCallback = (ActiveDevice device, List<int> values) =>
        onBleDataReceived(device, values);

    _prefs = await SharedPreferences.getInstance();

    getInfo();

    if (await shouldAutoShowDisclaimerSignInfo()) {
      showDisclaimerDialog();
    }
  }

  /// 简单处理BLE回复数据
  void onBleDataReceived(ActiveDevice device, List<int> values) {
    var format = values[2];
    int channel;
    int property;
    var one2one = (format & 0x03) != 0x03;
    if (one2one) {
      // 一对一
      channel = (values[4] & 0x0F);
      property = values[5];
    } else {
      // 非一对一
      channel = (values[6] & 0x0F);
      property = values[7];
    }

    int paramOffset = 6;
    if (!one2one) paramOffset = 8;

    // print("received bytes: " + Bytes.bytes2HexString(values, offset: 0, length: values.length));
    // print("onBleDataReceived, channel: $channel, property: $property, param offset: $paramOffset");

    if (channel == 2 && property == 1) {
      // model
      String hex = Bytes.bytes2HexString(values, offset: paramOffset, length: 3)
          .toUpperCase();

      currentModel.value = VehicleModel.fromHex(hex);
      _compactRegionsInfo(currentModel.value);
    } else if (channel == 2 && property == 2) {
      // sn
      if (values.length < 10 || values[paramOffset] == 0) {
        sn.value = "0000000000000000";
      } else {
        sn.value = Bytes.bytes2String(values, offset: paramOffset, length: 16);
      }
    } else if (channel == 2 && property == 9) {
      // rewrite designed max speed response
      if (values[paramOffset] == 0) {
      } else {
        toast("Failed!");
      }
    } else if (channel == 2 && property == 6) {
      // version
      int offset = paramOffset;

      // 版本号数量
      int count = (values.length - offset) ~/ (1 + 4 + 4);

      String versionInfo = "";
      for (int i = 0; i < count; i++) {
        int device = values[offset];

        offset++;
        String appVersion = formatVersion(values, offset, 4);
        offset += 4;
        offset += 4;

        String deviceName = getDeviceName(device);
        versionInfo += deviceName;
        versionInfo += ": ";
        versionInfo += appVersion;
        versionInfo += "\n";

        if (currentModel.value == inmotionS1 && device == 0x31) {
          // S1蓝牙板1.3.0以下不支持
          try {
            if (v1NotLowerThanV2("1.3.0", appVersion)) {
              donNotSupportChangeRegion = true;
              print("Don not support");
            }
          } catch (e) {
            print(e);
          }
        }
      }
      if (versionInfo.length > 2) {
        version.value = versionInfo.substring(0, versionInfo.length - 1);
      }
    } else if (channel == 1 && property == 5 && values[8] == 2) {
      // max speed
      int speed = Bytes.bytes2UInt16LE(values, paramOffset + 2);
      currentRegion.value = maxSpeed2Region(currentModel.value, speed);
    } else if (channel == 0x1 && property == 0x85 && values[8] == 2) {
      // write response
      if (values[9] == 0) {
        // success
        toast("Succeed");
      }
    } else if (channel == 1 &&
        property == (5 | 0x80) &&
        values[paramOffset + 2] == 2) {
      // 测试通道写第三档速度回复
    }
  }

  void disconnectCurrent({String? flag}) {
    BleManager().disconnect(flag: flag);
  }

  /// 获取车辆全部信息
  void getInfo() {
    showProgress("Loading...");
    Future.delayed(const Duration(milliseconds: 100), () {
      _bleManager.write(getSnBytes);
    }).then((nothing) {
      Future.delayed(const Duration(milliseconds: 500), () {
        _bleManager.write(getModelBytes);
      }).then((value) {
        Future.delayed(const Duration(milliseconds: 500), () {
          _bleManager.write(getVersionBytes);
        }).then((value) {
          Future.delayed(const Duration(milliseconds: 500), () {
            _bleManager.write(getRegionBytes);
          }).then((value) {
            dismissProgress();
          });
        });
      });
    });
  }

  @override
  void onClose() {
    super.onClose();
    BleManager().disconnect();
  }

  void toast(String msg) {
    Prompt.toast(msg);
  }

  /// 格式化版本号
  static String formatVersion(List<int> data, int offset, int length) {
    if (data.isEmpty || data.length < offset + length) {
      return "--";
    }
    return "${data[offset + 2 + 1] & 0xFF}.${data[offset + 2] & 0xFF}.${Bytes.bytes2UInt16LE(data, offset)}";
  }

  /// 获取设备号名
  static String getDeviceName(int device) {
    if (device == 0x31) return "Display Board";
    if (device == 0x32) return "Driver Board";
    return "Unknown";
  }

  /// 根据最大设计速度获取显示的地区名
  String maxSpeed2Region(VehicleModel model, int maxSpeed) {
    if (model == unknownModel) return "Unknown";
    if (model == inmotionS1 || model == inmotionC1) {
      if (maxSpeed == 2000) return regions[0];
      if (maxSpeed == 2500) return regions[1];
      if (maxSpeed == 3000) return regions[2];
      if (maxSpeed == 4000) return regions[3];
    } else if (model == inmotionC1Pro || model == inmotionC1Pro2) {
      if (maxSpeed == 2000) return regions[0];
      if (maxSpeed == 2500) return regions[1];
      if (maxSpeed == 3500 || maxSpeed == 3600) return regions[2];
      if (maxSpeed == 4000) return regions[3];
    }
    return "Unknown";
  }

  int lastClickRegionIndex = -1;

  Future<void> changeVehicleRegion(int regionIndex) async {
    List<int> bytes = [];
    switch (regionIndex) {
      case 0:
        bytes = setDesignedMaxSpeed20;
        break;
      case 1:
        bytes = setDesignedMaxSpeed25;
        break;
      case 2:
        if (currentModel.value == inmotionC1Pro ||
            currentModel.value == inmotionC1Pro2) {
          bytes = setDesignedMaxSpeed35;
        } else if (currentModel.value == inmotionE1) {
          bytes = setDesignedMaxSpeed40;
        } else {
          bytes = setDesignedMaxSpeed30;
        }
        break;
      case 3:
        bytes = setDesignedMaxSpeed40;
        break;
    }
    if (bytes.isEmpty) {
      toast("Error region, please restart this APP to re-change");
      return;
    }
    if (currentModel.value == inmotionS1) {
      _bleManager.write(getSafeSpeedLimitOffBytes);
      await Future.delayed(const Duration(milliseconds: 500));
    }
    _bleManager.write(bytes);

    showProgress("Changing...");
    lastClickRegionIndex = regionIndex;
    Future.delayed(const Duration(milliseconds: 500), () {
      if (currentModel.value != inmotionE1) {
        _bleManager.write(getRegionBytes);
      }
      lastClickRegionIndex = -1;
      dismissProgress();
    });
  }

  showDisclaimerDialog() {
    Prompt.showMessageDialog(
        "The software is provided as 'as is', without any warranty of any kind.\nThe developer shall not be responsible for any claim or damage that might arise while using this software.\nThe user himself is responsible for the correct usage and shall act with caution to prevent any damage of his own possessions or of others.\nPlease check local laws before riding your modded scooter on a public place.\nIn countries such as Germany and many others, modifying your scooter might void its permit",
        confirm: "I Agree", onConfirm: () {
      saveDisclaimerSignInfo();
    });
  }

  void saveDisclaimerSignInfo() async {
    List<String>? history = _prefs!.getStringList("sign_history");
    history ??= [];
    history.add(DateTime.now().millisecondsSinceEpoch.toString());

    _prefs!.setStringList("sign_history", history);
    _prefs!
        .setInt("latest_sign_timestamp", DateTime.now().millisecondsSinceEpoch);
  }

  Future<bool> shouldAutoShowDisclaimerSignInfo() async {
    int? timestamp = _prefs!.getInt("latest_sign_timestamp");
    return timestamp == null || timestamp == 0;
  }

  showProgress(String msg) {
    EasyLoading.show(status: msg);
  }

  dismissProgress() {
    EasyLoading.dismiss();
  }

  static bool v1NotLowerThanV2(String v1, String v2) {
    return Version.parse(v1) >= Version.parse(v2);
  }
}

var unknownModel = VehicleModel("", "Unknown");
var inmotionS1 = VehicleModel("0305", "INMOTION S1");
var inmotionC1 = VehicleModel("0307", "INMOTION C1");
var inmotionC1Pro = VehicleModel("030705", "INMOTION Air Pro");
var inmotionC1Pro2 = VehicleModel("030708", "INMOTION Air Pro");
var inmotionE1 = VehicleModel("0308", "INMOTION Climber");

class VehicleModel {
  String displayName;
  String hexCodePrefix;

  VehicleModel(this.hexCodePrefix, this.displayName);

  @override
  String toString() {
    return displayName;
  }

  static VehicleModel fromHex(String hex) {
    print("model hex: " + hex);
    if (hex.isEmpty) return unknownModel;
    if (hex.startsWith(inmotionS1.hexCodePrefix)) return inmotionS1;
    if (hex.startsWith(inmotionC1Pro2.hexCodePrefix)) return inmotionC1Pro2;
    if (hex.startsWith(inmotionC1Pro.hexCodePrefix)) return inmotionC1Pro;
    if (hex.startsWith(inmotionC1.hexCodePrefix)) return inmotionC1;
    if (hex.startsWith(inmotionE1.hexCodePrefix)) return inmotionE1;
    return unknownModel;
  }
}

class Bytes {
  static String bytes2String(List<int> bytes,
      {int offset = 0, int length = 0}) {
    return String.fromCharCodes(bytes, offset, offset + length);
  }

  static String bytes2HexString(List<int> bytes,
      {int offset = 0, int length = 0}) {
    String str = "";
    for (var i = offset; i < length + offset; i++) {
      str += ((bytes[i] < 0x10 ? "0" : "") + bytes[i].toRadixString(16));
    }
    return str;
  }

  static int bytes2UInt16LE(List<int> bytes, [int offset = 0]) {
    return (bytes[offset + 1] << 8) | bytes[offset];
  }
}
